<!DOCTYPE html><html lang="[&quot;zh-Hans&quot;,&quot;zh-CN&quot;,&quot;default&quot;]"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="PWA笔记"><meta name="keywords" content="pwa"><meta name="author" content="wenmu"><meta name="copyright" content="wenmu"><title>PWA笔记 | 温木的博客</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.6.1"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.6.1"><link rel="dns-prefetch" href="https://cdn.staticfile.org"><link rel="dns-prefetch" href="https://cdn.bootcss.com"><link rel="dns-prefetch" href="https://creativecommons.org"><link rel="dns-prefetch" href="http://ta.qq.com"><script>(function() {
   var hm = document.createElement("script");
   hm.src = "https://tajs.qq.com/stats?sId=&lt;script type=&quot;text/javascript&quot; src=&quot;http://tajs.qq.com/stats?sId=66523541&quot; charset=&quot;UTF-8&quot;&gt;&lt;/script&gt;";
   var s = document.getElementsByTagName("script")[0];
   s.parentNode.insertBefore(hm, s);
 })();</script><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  }
} </script><meta name="generator" content="Hexo 4.2.0"></head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#Web-App-Manifest"><span class="toc-number">1.</span> <span class="toc-text">Web App Manifest</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#缓存策略"><span class="toc-number">2.</span> <span class="toc-text">缓存策略</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#注意"><span class="toc-number">3.</span> <span class="toc-text">注意</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#工具-workbox"><span class="toc-number">4.</span> <span class="toc-text">工具 workbox</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#消息通知"><span class="toc-number">5.</span> <span class="toc-text">消息通知</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="https://i.loli.net/2020/02/27/xwaBoOj158MfNyq.jpg"></div><div class="author-info__name text-center">wenmu</div><div class="author-info__description text-center">记录一些在工作中或学习新知识时学到的一些知识点；正所谓好记性不如一个烂笔头，温故而知新！</div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">115</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">52</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">分类</span><span class="pull-right">30</span></a></div></div></div><div id="content-outer"><div class="no-bg" id="top-container"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">温木的博客</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus"><a class="site-page" href="/">首页</a><a class="site-page" href="/archives">文章</a><a class="site-page" href="/tags">标签</a><a class="site-page" href="/categories">分类</a></span></div><div id="post-info"><div id="post-title">PWA笔记</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2020-01-09</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/pwa/">pwa</a></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><p><a href="https://lavas.baidu.com/pwa/README" target="_blank" rel="noopener">https://lavas.baidu.com/pwa/README</a></p>
<p>Progressive Web App, 简称 PWA，是提升 Web App 的体验的一种新方法，能给用户原生应用的体验。</p>
<p>PWA 能做到原生应用的体验不是靠特指某一项技术，而是经过应用一些新技术进行改进，在安全、性能和体验三个方面都有很大提升，PWA 本质上是 Web App，借助一些新技术也具备了 Native App 的一些特性，兼具 Web App 和 Native App 的优点。</p>
<p>PWA 的主要特点包括下面三点：</p>
<p>可靠 - 即使在不稳定的网络环境下，也能瞬间加载并展现<br>体验 - 快速响应，并且有平滑的动画响应用户的操作<br>粘性 - 像设备上的原生应用，具有沉浸式的用户体验，用户可以添加到桌面。</p>
<a id="more"></a>

<h3 id="Web-App-Manifest"><a href="#Web-App-Manifest" class="headerlink" title="Web App Manifest"></a>Web App Manifest</h3><p>manifest 其实就是一个 json 文件<code>manifest.json</code>。它的作用：</p>
<ul>
<li>将 PWA 站点添加至桌面</li>
<li>设置主屏幕启动时的过渡画面</li>
<li>隐藏浏览器的相关 UI</li>
</ul>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"short_name"</span>: <span class="string">"短名称"</span>,</span><br><span class="line">    <span class="attr">"name"</span>: <span class="string">"这是一个完整名称"</span>,</span><br><span class="line">    <span class="attr">"icon"</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">"src"</span>: <span class="string">"icon.png"</span>,</span><br><span class="line">            <span class="attr">"type"</span>: <span class="string">"image/png"</span>,</span><br><span class="line">            <span class="attr">"sizes"</span>: <span class="string">"48x48"</span></span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"start_url"</span>: <span class="string">"index.html"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在文件中引用</span></span><br><span class="line">&lt;link rel="manifest" href="./manifest.json"&gt;</span><br></pre></td></tr></table></figure>

<p>###service worker<br>创建一个注册文件<code>sw-register.js</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="string">"serviceWorker"</span> <span class="keyword">in</span> navigator) &#123;</span><br><span class="line">  <span class="comment">// 因为service worker需要启动一个新的线程，所以为了避免阻塞进程，在loader事件中执行。可以避免一些低配置的设置扛不住</span></span><br><span class="line">  <span class="built_in">window</span>.addEventListener(<span class="string">"load"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    navigator.serviceWorker</span><br><span class="line">      .register(<span class="string">"/sw.js"</span>, &#123; <span class="attr">scope</span>: <span class="string">"/"</span> &#125;)</span><br><span class="line">      .then(<span class="function"><span class="keyword">function</span>(<span class="params">registration</span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 注册成功</span></span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"ServiceWorker registration successful with scope: "</span>, registration.scope);</span><br><span class="line">      &#125;)</span><br><span class="line">      .catch(<span class="function"><span class="keyword">function</span>(<span class="params">err</span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 注册失败:(</span></span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"ServiceWorker registration failed: "</span>, err);</span><br><span class="line">      &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">  navigator.serviceWorker.oncontrollerchange = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    showToast(<span class="string">"页面已更新！"</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (navigator.online) &#123;</span><br><span class="line">    showToast(<span class="string">"网络已断开，内容可能已过期"</span>);</span><br><span class="line">    <span class="built_in">window</span>.addEventListener(<span class="string">"online"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      showToast(<span class="string">"网络已连接"</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>sw.js 中注册 service worker 的事件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听 service worker 的 install 事件</span></span><br><span class="line"><span class="keyword">this</span>.addEventListener(<span class="string">'install'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>&#123;</span><br><span class="line">    <span class="comment">//  event.waitUntil 回调函数 保证里面的所有操作执行完，安装才会结束。</span></span><br><span class="line">    event.waitUntil(</span><br><span class="line">        <span class="comment">// 操作 CacheStorage 缓存，使用 caches.open() 打开一个对应缓存空间。</span></span><br><span class="line">        caches.open(<span class="string">'my-test-cache-v1'</span>).then(<span class="function"><span class="keyword">function</span> (<span class="params">cache</span>) </span>&#123;</span><br><span class="line">            <span class="comment">// 通过 cache 缓存对象的 addAll 方法设置缓存列表</span></span><br><span class="line">            <span class="keyword">return</span> cache.addAll([</span><br><span class="line">                <span class="string">'/'</span>,</span><br><span class="line">                <span class="string">'/index.html'</span>,</span><br><span class="line">                <span class="string">'/main.css'</span>,</span><br><span class="line">                <span class="string">'/main.js'</span>,</span><br><span class="line">                <span class="string">'/image.jpg'</span></span><br><span class="line">            ]);</span><br><span class="line">        &#125;).then(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;self.skipWaiting()&#125;);</span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 安装成功后就该激活</span></span><br><span class="line"><span class="keyword">this</span>.addEventListener(<span class="string">'activate'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>&#123;</span><br><span class="line">    event.waitUntil(</span><br><span class="line">        <span class="built_in">Promise</span>.all([</span><br><span class="line">            <span class="comment">// 使首次加载的页面可以直接被控制</span></span><br><span class="line">            <span class="keyword">this</span>.clients.claim();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 激活后，获取缓存列表</span></span><br><span class="line">       caches.keys.then(<span class="function"><span class="keyword">function</span>(<span class="params">cacheList</span>)</span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">Promise</span>.all(</span><br><span class="line">                cacheList.map(<span class="function"><span class="keyword">function</span>(<span class="params">cacheName</span>)</span>&#123;</span><br><span class="line">                      <span class="comment">// 删除不必要的缓存</span></span><br><span class="line">                      <span class="keyword">if</span>(cacheName!==<span class="string">'my-test-cache-v1'</span>)&#123;</span><br><span class="line">                          <span class="keyword">return</span> catches.delete(cacheName)</span><br><span class="line">                      &#125;</span><br><span class="line">                &#125;)</span><br><span class="line">            );</span><br><span class="line">      &#125;);</span><br><span class="line">        ]);</span><br><span class="line"></span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>service worker 激活后，就获取了对页面的所有控制权，但是 service worker 只能控制激活后打开的页面，因此首次激活这个 service worker 的页面是无法被控制的，因此如果首次加载的页面也想被 service worker 控制，则需要刷新下页面才行，为了实现首页和激活后打开的页面一样直接就被 service worker 控制，就需要使用<code>this.clients.claim()</code></p>
<blockquote>
<p>在 install 中缓存资源，在 activate 中删除没必要的缓存，在 fetch 中获取缓存中的资源，fetch 中也可以缓存资源。</p>
</blockquote>
<h3 id="缓存策略"><a href="#缓存策略" class="headerlink" title="缓存策略"></a>缓存策略</h3><ul>
<li>Cache only<br>资源直接从缓存中获取，获取不到，则请求失败。该模式假定资源之前已被缓存，可能 install 期间就做了缓存，比较适合静态资源。</li>
<li>Network only<br>只从网络返回，适合前端用户行为日志大点之类的请求。</li>
<li>Cache，falling back to Network<br>优先从 cache 返回内容，如果失败则从网络请求。</li>
<li>Network, falling back to cache<br>优先从网络返回内容，如果失败了则从缓存获取。适用于频繁更新的内容，希望用户总是看到最新的内容。</li>
</ul>
<h3 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h3><ul>
<li>避免使用 http 缓存</li>
<li>最长 24 小时</li>
<li>避免缓存跨域资源</li>
</ul>
<h3 id="工具-workbox"><a href="#工具-workbox" class="headerlink" title="工具 workbox"></a>工具 workbox</h3><p>service worker 的工具<a href="https://github.com/googlechrome/workbox" target="_blank" rel="noopener">https://github.com/googlechrome/workbox</a></p>
<h3 id="消息通知"><a href="#消息通知" class="headerlink" title="消息通知"></a>消息通知</h3><ul>
<li>查看通知权限<br>在控制台输入下面命令，就可以查看当前网站的通知权限</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Notification.permission;</span><br></pre></td></tr></table></figure>

<p>权限值有三个：</p>
<blockquote>
<ul>
<li>default</li>
<li>granted</li>
<li>denied</li>
</ul>
</blockquote>
<ul>
<li>请求设置通知权限</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Notification.requestPermission().then(<span class="function"><span class="params">permission</span> =&gt;</span> <span class="built_in">console</span>.log(permission));</span><br></pre></td></tr></table></figure>

<p>requestPermission 静态方法返回的是一个 promise 对象</p>
<ul>
<li>发送通知<br>权限设置允许后，就可以发送通知了。<br>Notification 的一个实例，就相当于发送一个通知。</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> Notification(<span class="string">"this is title"</span>, &#123; <span class="attr">body</span>: <span class="string">"this is content."</span> &#125;);</span><br></pre></td></tr></table></figure>

<p>###service worker 中发送通知<br>在 service worker 中是不允许弹出提示，所以通知权限默认是 denied，不是 default。<code>（可以在控制台中切换成service worker环境进行测试）</code><br>因为 service worker 控制是整个项目的请求，所以它不知道是哪个页面的通知。另外它也不允许控制 dom。<br>那该如何在 service worker 中发送通知呢？<br>只需要在页面中设置通知权限，然后在 service worker 中发送请求即可。<br>service worker 发送通知和在页面中发送通知不同。在 service worker 中不能把 Notification 当成构造函数来创建通知，使用下面方式：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">self.registration.showNotification(&quot;hello&quot;)</span><br></pre></td></tr></table></figure>

<p><code>registration</code>就是注册 service worker 时生成的对象。</p>
<blockquote>
<p>删除除 serviceWorker.js 之外的所有文件和文件夹，如果都是文件，可不加-dr</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls | grep -v serviceWorker.js | xargs rm -dr</span><br></pre></td></tr></table></figure>
</blockquote>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">wenmu</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://blog.wangpengpeng.site/2020/01/09/PWA%E7%AC%94%E8%AE%B0/">http://blog.wangpengpeng.site/2020/01/09/PWA%E7%AC%94%E8%AE%B0/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://blog.wangpengpeng.site">温木的博客</a>！</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/pwa/">pwa</a></div><div class="post-qr-code"><div class="post-qr-code-item"><img class="post-qr-code__img" src="https://i.loli.net/2020/02/27/hOBzIxFDJMCrmtN.jpg"><div class="post-qr-code__desc">微信打赏</div></div></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2020/01/09/React-%E6%96%B0%E7%89%B9%E6%80%A7Hooks/"><i class="fa fa-chevron-left">  </i><span>React-新特性Hooks</span></a></div><div class="next-post pull-right"><a href="/2020/01/09/React%E6%96%B0%E7%89%B9%E6%80%A7-memo/"><span>React新特性-memo</span><i class="fa fa-chevron-right"></i></a></div></nav><div id="vcomment"></div><script src="https://cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><script src="https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js"></script><script>var notify = 'false' == true ? true : false;
var verify = 'false' == true ? true : false;
var GUEST_INFO = ['nick','mail','link'];
var guest_info = 'nick,mail,link'.split(',').filter(function(item){
  return GUEST_INFO.indexOf(item) > -1
});
guest_info = guest_info.length == 0 ? GUEST_INFO :guest_info;
window.valine = new Valine({
  el:'#vcomment',
  notify:notify,
  verify:verify,
  appId:'U1QqpeQNuBnRsfygHk1GQ30q-gzGzoHsz',
  appKey:'dunDQzEseDzgsKgw4qLQcog1',
  placeholder:'支持markdown！',
  avatar:'wavatar',
  guest_info:guest_info,
  pageSize:'20',
  lang: 'zh-cn'
})</script></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2019 - 2020 By wenmu</div><div class="framework-info"><span>驱动 - </span><a href="http://hexo.io" target="_blank" rel="noopener"><span>Hexo</span></a><span class="footer-separator">|</span><span>主题 - </span><a href="https://github.com/Molunerfinn/hexo-theme-melody" target="_blank" rel="noopener"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv">该文访问量<span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.6.1"></script><script src="/js/fancybox.js?version=1.6.1"></script><script src="/js/sidebar.js?version=1.6.1"></script><script src="/js/copy.js?version=1.6.1"></script><script src="/js/fireworks.js?version=1.6.1"></script><script src="/js/transition.js?version=1.6.1"></script><script src="/js/scroll.js?version=1.6.1"></script><script src="/js/head.js?version=1.6.1"></script><script>if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
}</script></body></html>